package com.gitee.wsl.coroutines.event


import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.FlowCollector
import kotlin.coroutines.coroutineContext
//import kotlin.js.JsName

/**
 * Marker interface for all events which can be dispatched with evas.
 *
 *
 * ### Example Usages (Compose)
 * #### An event representing the user logging out of the application:
 * ```kotlin
 * data object UserLoggedOutEvent: Event
 *               //                  ^
 *               // Marked as 'Event', can be dispatched
 * ```
 *
 * ### Emitting this event from coroutine context
 * ```
 * suspend fun logout() {
 *     deleteUserData()
 *     UserLoggedOutEvent.emit()
 *                   //    ^
 *                   // Will dispatch the Events bus installed in the current coroutine context
 * }
 * ```
 *
 * #### Emitting this event in compose
 * ```
 * @Composable
 * fun LogoutButton() {
 *     Button(
 *         onClick = EvasLaunching {
 *             UserLoggedOutEvent.emit()
 *         }
 *     ) {
 *         // ...
 *     }
 * }
 *
 * ```
 */
interface CoroutineEvent

/**
 * Sends the current [CoroutineEvent] by dispatching it to the [Events] instance currently installed
 * in the current coroutine context.
 *
 * ☝️ Note: This method will suspend until all listener coroutines have finished processing this event:
 * e.g.
 * ```
 * suspend fun example() = coroutineScope {
 *    collectEventsAsync<MyEvent> {
 *        delay(5.seconds)
 *        print("First collector finished")
 *
 *    }
 *
 *    collectEventsAsync<MyEvent> {
 *        delay(1.seconds)
 *        print("Second collector finished")
 *    }
 *
 *    MyEvent().emit()
 *             // ^
 *             // Will suspend ~ 5 seconds as the first
 *             // collector seems to be the slowest, taking ~ 5 seconds
 *
 *    println(".emit() finished")
 *
 *    // Will print
 *    // > Second collector finished
 *    // > First collector finished
 *    // > .emit() finished
 * }
 * ```
 *
 * @throws MissingEventsException if there is no [Events] instance installed in the current coroutine context.
 * See [Events] documentation to learn more.
 * See [emitAsync] for emitting an event without waiting for all listeners to finish their work.
 *
 */
suspend fun CoroutineEvent.emit() {
    currentCoroutineContext().eventsOrThrow.emit(this)
}

/**
 * Similar to [CoroutineEvent.emit] with one distinction:
 * While [CoroutineEvent.emit] will suspend until all event listener coroutines have finished,
 * this [CoroutineEvent.emitAsync] implementation will not suspend.
 *
 * Events can be emitted async even outside suspend functions, by calling
 * [Events.emitAsync] directly
 *
 * @throws MissingEventsException if there is no [Events] instance installed in the current coroutine context.
 */
 suspend fun CoroutineEvent.emitAsync() {
    currentCoroutineContext().eventsOrThrow.emitAsync(this)
}


val <T> FlowCollector<T>.emit: suspend T.() -> Unit get() = { emit(this) }

